/*
 * Copyright 2010-2012 Susanta Tewari. <freecode4susant@users.sourceforge.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package coalescent.is.cef.command;

import cef.command.Command;
import cef.command.CommandException;
import coalescent.is.cef.event.RunSamplers_Evt;
import com.google.common.base.Stopwatch;
import com.google.common.base.Ticker;
import commons.is.Sampler;

import java.lang.management.ManagementFactory;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static commons.util.ExceptionUtil.throwArgEx;

/**
 * Estimates a quantity using a number of importance samplers. Configurations for samplers include:
 * sample size, display-window size and information on exact value, if known (testing scenarios).
 *
 * @author Susanta Tewari
 * @version 1.4.2
 * @history Created on Jul 31, 2012.
 * @since 1.3.0
 */
public class RunSamplers_Cmd extends Command<RunSamplers_Evt> {

    private static final boolean threadCpuTimeEnabled =
        ManagementFactory.getThreadMXBean().isThreadCpuTimeEnabled();
    private final Map<String, Sampler<?>> samplers_map;
    private final Map<String, Thread> thread_map;
    private final Map<String, Stopwatch> watch_map;

    /**
     * @param event event for this command
     * @throws IllegalArgumentException if {@code event.getSamplerNames.size < 1}
     */
    public RunSamplers_Cmd(final RunSamplers_Evt event) {

        super(event);

        final Collection<Sampler<?>> samplers = event.getSamplers();

        throwArgEx(samplers.size() < 1, "at least one sampler is needed");

        samplers_map = new HashMap<>(samplers.size());
        thread_map   = new HashMap<>(samplers.size());
        watch_map    = new HashMap<>(samplers.size());

        for (final Sampler<?> sampler : samplers) {

            final String iSamplerName = sampler.getName();

            samplers_map.put(iSamplerName, sampler);
            watch_map.put(iSamplerName, of_StopWatch());
        }
    }

    @Override
    protected void executeImpl() throws CommandException, InterruptedException {

        for (final Sampler<?> sampler : samplers_map.values()) {

            final String name     = sampler.getName();
            final Stopwatch watch = watch_map.get(name);
            final Thread thread   = new Thread(new Runnable() {

                @Override
                public void run() {

                    watch.start();

                    try {
                        sampler.run();
                    } catch (InterruptedException e) {
                        return;
                    }

                    watch.stop();
                }

            });

            thread_map.put(name, thread);
            thread.start();
        }

        for (Thread thread : thread_map.values()) {

            try {
                thread.join();
            } catch (InterruptedException e) {

                for (Thread t : thread_map.values()) {
                    t.interrupt();
                }

                throw new InterruptedException();
            }
        }
    }

    /**
     * The names of the importance samplers can be used for querying about various outputs.
     *
     * @return names of the importance samplers
     */
    public List<String> getSamplerNames() {
        return new ArrayList<>(samplers_map.keySet());
    }

    /**
     * Tests if the Java virtual machine implementation supports CPU time measurement for any thread.
     *
     * @return true if the Java virtual machine supports CPU time measurement for any thread;
     * false otherwise.
     */
    public static boolean isThreadCpuTimeEnabled() {
        return threadCpuTimeEnabled;
    }

    private static Stopwatch of_StopWatch() {

        if (!threadCpuTimeEnabled) return new Stopwatch();

        return new Stopwatch(new Ticker() {

            @Override
            public long read() {
                return ManagementFactory.getThreadMXBean().getCurrentThreadCpuTime();
            }

        });
    }


    // <editor-fold desc="Measures">

    /**
     *
     * @param sampler one of the available samplers
     * @return sample size
     * @see commons.is.Sampler#nowSampleSize()
     */
    public long getSampleSize(String sampler) {

        checkSampler(sampler);

        return samplers_map.get(sampler).nowSampleSize();
    }

    /**
     * @param sampler one of the available samplers
     * @return mean for the specified sampler
     */
    public BigDecimal getISMean(final String sampler) {

        checkSampler(sampler);

        return samplers_map.get(sampler).nowMean();
    }

    /**
     * @param sampler one of the available samplers
     * @return SE for the specified sampler
     */
    public BigDecimal getIS_SE(final String sampler) {

        checkSampler(sampler);

        return samplers_map.get(sampler).nowStdError();
    }

    /**
     * @param sampler one of the available samplers
     * @return ESS for the specified sampler
     */
    public BigDecimal getIS_ESS(final String sampler) {

        checkSampler(sampler);

        return samplers_map.get(sampler).nowESS();
    }

    /**
     * @param sampler one of the available samplers
     * @return elapsed time (in seconds) since the sampler started
     */
    public int getElapsedTime(final String sampler) {

        checkSampler(sampler);

        return (int) watch_map.get(sampler).elapsed(TimeUnit.SECONDS);

    }    // </editor-fold>

    public static boolean isCancellable() {
        return true;
    }

    private void checkSampler(String sampler) {
        throwArgEx(!samplers_map.containsKey(sampler), "invalid sampler: " + sampler);
    }
}
